﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Browser;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using ICSharpCode.SharpZipLib.GZip;
using Newtonsoft.Json;
using Tricom.Tricommerce.Domain;
using Task = System.Threading.Tasks.Task;

namespace SilverlightRest
{
    internal class RestClient
    {
        internal static async Task PostWithRetry<TApiResultType, TControllerResultType>(
            string serviceUrl,
            object parameter,
            Action<TControllerResultType> callback,
            Action<Exception> errorCallback,
            Func<TApiResultType, TControllerResultType> converter,
            bool requiresAuthentication = true)
        {
            try
            {
                var result = await PostWithRetry<TApiResultType>(serviceUrl, parameter, requiresAuthentication);
                callback(converter(result));
            }
            catch (Exception e) { errorCallback(e); }
        }

        internal static async Task PostWithRetry<T>(
            string serviceUrl,
            object parameter,
            Action<T> callback,
            Action<Exception> errorCallback,
            bool requiresAuthentication = true)
        {
            try
            {
                var result = await PostWithRetry<T>(serviceUrl, parameter, requiresAuthentication);
                callback(result);
            }
            catch (Exception e) { errorCallback(e); }
        }

        internal static async Task PostWithRetry(
            string serviceUrl,
            object parameter,
            Action callback,
            Action<Exception> errorCallback,
            bool requiresAuthentication = true)
        {
            try
            {
                await PostWithRetry(serviceUrl, parameter, requiresAuthentication);
                callback();
            }
            catch (Exception e) { errorCallback(e); }
        }

        internal static async Task<T> PostWithRetry<T>(string serviceUrl, object parameter, bool requiresAuthentication = true)
        {
            var retryCount = RetryCount;
            while (true)
            {
                try
                {
                    var response = await DoRequest(serviceUrl, parameter, requiresAuthentication);
                    var responseSerialized = await response.Content.ReadAsStringAsync();
                    response.EnsureSuccessStatusCode();
                    return Deserialize<T>(responseSerialized);
                }
                catch
                {
                    if (retryCount == 0)
                        throw;
                    retryCount--;
                }
            }
        }

        internal static async Task PostWithRetry(string serviceUrl, object parameter, bool requiresAuthentication = true)
        {
            var retryCount = RetryCount;
            while (true)
            {
                try
                {
                    var response = await DoRequest(serviceUrl, parameter, requiresAuthentication);
                    response.EnsureSuccessStatusCode();
                    return;
                }
                catch
                {
                    if (retryCount == 0)
                        throw;
                    retryCount--;
                }
            }
        }

        #region Private

        private const int RetryCount = 2;

        private static string Serialize(object serializeMe)
        {
            var serializerSettings = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                Formatting = Formatting.Indented,
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                TypeNameHandling = TypeNameHandling.Objects
            };
            return JsonConvert.SerializeObject(serializeMe, serializerSettings);
        }

        private static T Deserialize<T>(string json)
        {
            var jsonSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects,
                ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
                ObjectCreationHandling = ObjectCreationHandling.Auto,
                Binder = new TypeNameSerializationBinder()
            };
            return JsonConvert.DeserializeObject<T>(json, jsonSettings);
        }

        private static string _token;

        private static async Task<HttpResponseMessage> DoRequest(string serviceUrl, object parameter, bool requiresAuthentication = true)
        {
            WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
            WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
           
            var handler = new HttpClientHandler();
            if (handler.SupportsAutomaticDecompression && Environment.OSVersion.Platform.ToString() != "MacOSX")
            {
                handler.AutomaticDecompression = DecompressionMethods.GZip;
            }
           
            var httpClient = new HttpClient(handler)
            {
                BaseAddress = new Uri("http://10.145.8.38/SilverlightRest.Web/api/")
            };

            if (requiresAuthentication && !string.IsNullOrWhiteSpace(_token))
                httpClient.DefaultRequestHeaders.Add("token", _token);

            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            httpClient.DefaultRequestHeaders.Add("compression", "gzip");

            var serializedParameter = Serialize(parameter);
            var httpContent = new StringContent(serializedParameter, Encoding.UTF8, "application/json");

            var compressedContent = await CompressAsync(httpContent);
            
            var response = await httpClient.PostAsync(serviceUrl, compressedContent).ConfigureAwait(false);

            if (requiresAuthentication && !string.IsNullOrWhiteSpace(response.Headers.GetValues("token").First()))
                _token = response.Headers.GetValues("token").First();

            //MessageBox.Show("hvasså");

            return response;
        }      

        private static async Task<StreamContent> CompressAsync(HttpContent content)
        {
            var ms = new MemoryStream();
            using (var gzipStream = new GZipOutputStream(ms))
            {
                var bytes = await content.ReadAsByteArrayAsync();
                gzipStream.Write(bytes, 0, bytes.Length);
                gzipStream.IsStreamOwner = false;
            }
            ms.Position = 0;
            var compressedStreamContent = new StreamContent(ms);
            compressedStreamContent.Headers.ContentType = content.Headers.ContentType;
            compressedStreamContent.Headers.Add("Content-Encoding", "gzip");

            return compressedStreamContent;
        }     

        /// <summary>
        /// Custom SerializationBinder to handle the fact that the domain exists in two separate assemblies due to the linked Silverlight domain classes
        /// </summary>
        private class TypeNameSerializationBinder : SerializationBinder
        {
            public override Type BindToType(string assemblyName, string typeName)
            {
                string resolvedTypeName;
                if (assemblyName == "mscorlib")
                {
                    resolvedTypeName = string.Format("{0},{1}", typeName, assemblyName);
                    resolvedTypeName = resolvedTypeName.Replace("MyDomain]", "MyDomainSilverlight, Version=1.0.0.0]");
                }
                else
                    resolvedTypeName = string.Format("{0}, MyDomainSilverlight, Version=1.0.0.0, Culture=neutral", typeName);

                return Type.GetType(resolvedTypeName, true);
            }
        }

        #endregion
    }
}
